package cn.song.freight.aspect;

import cn.hutool.core.lang.UUID;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import cn.song.freight.annotation.OperationLog;
import cn.song.freight.domain.dto.OperationLogAddDTO;
import cn.song.freight.service.OperationLogService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.concurrent.CompletableFuture;

/**
 * 操作日志AOP切面
 * <pre>
 * 该切面用于自动拦截标记了{@link OperationLog}注解的方法，
 * 记录操作日志信息，包括操作前后的数据变化、请求信息、执行时间等。
 *
 * 核心功能：
 * - 自动拦截标记了@OperationLog注解的方法
 * - 记录操作前后的数据状态
 * - 记录请求信息（IP、URL、参数等）
 * - 记录执行时间和操作状态
 * - 异步记录日志，不影响业务性能
 * - 异常处理机制，确保业务操作不受影响
 *
 * 日志记录内容：
 * - 基本信息：操作人、操作时间、模块类型、操作类型
 * - 业务信息：业务ID、业务名称、操作描述
 * - 请求信息：IP地址、URL、请求方法、请求参数
 * - 执行信息：执行时间、操作状态、异常信息
 * - 数据变更：根据注解配置记录参数和返回值
 *
 * @author SongRenShuo
 * @version 1.0.0
 * @since 2025-08-29
 */
@Slf4j
@Aspect
@Component
@Order(1)
@RequiredArgsConstructor
public class OperationLogAspect {

    /**
     * 操作日志服务
     */
    private final OperationLogService operationLogService;

    /**
     * 环绕通知，拦截标记了@OperationLog注解的方法
     *
     * @param joinPoint 连接点
     * @param operationLog 操作日志注解
     * @return 方法执行结果
     * @throws Throwable 方法执行异常
     */
    @Around("@annotation(operationLog)")
    public Object around(ProceedingJoinPoint joinPoint, OperationLog operationLog) throws Throwable {
        // 生成链路追踪ID
        String traceId = UUID.fastUUID().toString();

        // 记录开始时间
        long startTime = System.currentTimeMillis();

        // 获取方法签名
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();

        // 获取请求信息
        HttpServletRequest request = getHttpServletRequest();

        // 构建日志基础信息
        OperationLogAddDTO.OperationLogAddDTOBuilder logBuilder = OperationLogAddDTO.builder()
                .traceId(traceId)
                .moduleType(operationLog.moduleType().getCode())
                .operationType(operationLog.operationType().getCode())
                .operationDescription(operationLog.description())
                .operator(getCurrentOperator())
                .executionTime(0)
                .status(1); // 默认成功状态

        // 记录请求信息
        if (request != null) {
            logBuilder.requestMethod(request.getMethod())
                    .requestUrl(request.getRequestURL().toString())
                    .requestIp(getClientIpAddress(request))
                    .userAgent(request.getHeader("User-Agent"));
        }

        // 记录请求参数
        if (operationLog.recordRequestParams()) {
            Object[] args = joinPoint.getArgs();
            if (args != null && args.length > 0) {
                try {
                    logBuilder.requestParams(JSONUtil.toJsonStr(args));

                    // 提取业务ID和业务名称
                    extractBusinessInfo(args, operationLog, logBuilder);
                } catch (Exception e) {
                    log.warn("记录请求参数失败: {}", e.getMessage());
                }
            }
        }

        Object result;

        try {
            // 执行原方法
            result = joinPoint.proceed();

            // 记录返回结果（作为操作后数据）
            if (operationLog.recordResult() && result != null) {
                try {
                    logBuilder.afterData(JSONUtil.toJsonStr(result));
                } catch (Exception e) {
                    log.warn("记录返回结果失败: {}", e.getMessage());
                }
            }

            // 记录响应结果（接口响应体）
            if (operationLog.recordResponseResult() && result != null) {
                try {
                    logBuilder.responseResult(JSONUtil.toJsonStr(result));
                } catch (Exception e) {
                    log.warn("记录响应结果失败: {}", e.getMessage());
                }
            }

        } catch (Exception e) {
            // 设置失败状态
            logBuilder.status(2).errorMessage(e.getMessage());
            throw e;
        } finally {
            // 计算执行时间
            long executionTime = System.currentTimeMillis() - startTime;
            logBuilder.executionTime((int) executionTime);

            // 异步记录操作日志
            recordOperationLogAsync(logBuilder.build(), operationLog.ignoreException());
        }

        return result;
    }

    /**
     * 异步记录操作日志
     *
     * @param logDTO 操作日志数据
     * @param ignoreException 是否忽略异常
     */
    private void recordOperationLogAsync(OperationLogAddDTO logDTO, boolean ignoreException) {
        CompletableFuture.runAsync(() -> {
            try {
                operationLogService.recordOperationLog(logDTO);
            } catch (Exception e) {
                if (ignoreException) {
                    log.error("异步记录操作日志失败: {}", e.getMessage());
                } else {
                    log.error("异步记录操作日志失败", e);
                    throw new RuntimeException("操作日志记录失败", e);
                }
            }
        });
    }

    /**
     * 提取业务信息（业务ID和业务名称）
     *
     * @param args 方法参数
     * @param operationLog 操作日志注解
     * @param logBuilder 日志构建器
     */
    private void extractBusinessInfo(Object[] args, OperationLog operationLog,
                                     OperationLogAddDTO.OperationLogAddDTOBuilder logBuilder) {
        try {
            // 提取业务ID
            if (StrUtil.isNotBlank(operationLog.businessIdField())) {
                Object businessId = extractFieldValue(args, operationLog.businessIdField());
                if (businessId != null) {
                    logBuilder.businessId(businessId.toString());
                }
            }

            // 提取业务名称
            if (StrUtil.isNotBlank(operationLog.businessNameField())) {
                Object businessName = extractFieldValue(args, operationLog.businessNameField());
                if (businessName != null) {
                    logBuilder.businessName(businessName.toString());
                }
            }
        } catch (Exception e) {
            log.warn("提取业务信息失败: {}", e.getMessage());
        }
    }

    /**
     * 从参数中提取字段值
     * 支持嵌套属性，如：user.name、area.provinceName等
     *
     * @param args 方法参数数组
     * @param fieldPath 字段路径
     * @return 字段值
     */
    private Object extractFieldValue(Object[] args, String fieldPath) {
        for (Object arg : args) {
            if (arg == null) {
                continue;
            }

            try {
                Object value = getFieldValueByPath(arg, fieldPath);
                if (value != null) {
                    return value;
                }
            } catch (Exception e) {
                // 继续尝试下一个参数
                log.warn("从参数中提取字段值失败: {}", e.getMessage());
            }
        }
        return null;
    }

    /**
     * 根据字段路径获取对象的属性值
     * 支持嵌套属性访问，如：user.name
     *
     * @param obj 对象
     * @param fieldPath 字段路径
     * @return 属性值
     */
    private Object getFieldValueByPath(Object obj, String fieldPath) {
        if (obj == null || StrUtil.isBlank(fieldPath)) {
            return null;
        }

        String[] paths = fieldPath.split("\\.");
        Object currentObj = obj;

        for (String path : paths) {
            if (currentObj == null) {
                return null;
            }

            try {
                currentObj = ReflectUtil.getFieldValue(currentObj, path);
            } catch (Exception e) {
                // 尝试通过getter方法获取
                try {
                    String getterName = "get" + StrUtil.upperFirst(path);
                    currentObj = ReflectUtil.invoke(currentObj, getterName);
                } catch (Exception ex) {
                    return null;
                }
            }
        }

        return currentObj;
    }

    /**
     * 获取HttpServletRequest对象
     *
     * @return HttpServletRequest对象
     */
    private HttpServletRequest getHttpServletRequest() {
        try {
            ServletRequestAttributes attributes =
                    (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            return attributes != null ? attributes.getRequest() : null;
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * 获取当前操作人
     * TODO: 这里需要根据实际的用户认证系统来实现
     * 可以从Security上下文、Session、JWT Token等方式获取
     *
     * @return 操作人标识
     */
    private String getCurrentOperator() {
        HttpServletRequest request = getHttpServletRequest();
        if (request != null) {
            // 尝试从请求头获取操作人信息
            String operator = request.getHeader("operator");
            if (StrUtil.isNotBlank(operator)) {
                return operator;
            }

            // 尝试从参数获取操作人信息
            operator = request.getParameter("operator");
            if (StrUtil.isNotBlank(operator)) {
                return operator;
            }
        }

        // 默认返回系统操作人（可根据实际情况调整）
        return "system";
    }

    /**
     * 获取客户端真实IP地址
     *
     * @param request HTTP请求对象
     * @return 客户端IP地址
     */
    private String getClientIpAddress(HttpServletRequest request) {
        String[] headers = {
                "X-Forwarded-For",
                "X-Real-IP",
                "Proxy-Client-IP",
                "WL-Proxy-Client-IP",
                "HTTP_X_FORWARDED_FOR",
                "HTTP_X_FORWARDED",
                "HTTP_X_CLUSTER_CLIENT_IP",
                "HTTP_CLIENT_IP",
                "HTTP_FORWARDED_FOR",
                "HTTP_FORWARDED",
                "HTTP_VIA",
                "REMOTE_ADDR"
        };

        for (String header : headers) {
            String ip = request.getHeader(header);
            if (StrUtil.isNotBlank(ip) && !"unknown".equalsIgnoreCase(ip)) {
                // 多个IP地址时取第一个
                if (ip.contains(",")) {
                    ip = ip.split(",")[0].trim();
                }
                return ip;
            }
        }

        return request.getRemoteAddr();
    }
}
